﻿// 异步编程： https://docs.microsoft.com/zh-cn/dotnet/csharp/async
// 深入了解异步 https://docs.microsoft.com/zh-cn/dotnet/standard/async-in-depth
// C# async与await的使用说明 https://blog.csdn.net/codingriver/article/details/83342267
namespace CSharpNewFeatures;

/// <summary>
/// 多线程相关测试。
/// </summary>
public class Demo_MultiThreading : CsharpDemoBase
{
    public bool testMode = true;

    public override async void Run()
    {
        if (testMode)
        {

            cancellation_token_resource_linked_cancel();
        }
        else
        {
            Is_Cancellation_Requested_check();
            task_no_async_no_return();
            task_no_async_with_return();
            task_await_check_isCompleted();
            async_typical_call_no_returned();
            async_typical_call_wait_returned_value();
            async_multiple_tasks_callback();
            async_callback_will_complete_remaining_task_demo();
            async_callback_will_complete_remaining_task_demo11();
            async_crawl_web_with_cancellation_and_try_catch(100);
            async_crawl_web_with_cancellation_and_try_catch(2000);
            cancellation_token_source_useage("https://www.baidu.com");

            cancellation_token_resource_direct_cancel();
            cancellation_token_resource_delayed_cancel();
        }

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }

    private void HeavyTask_NoReturn()
    {
        print("Start");
        for (int i = 0; i <5; i++)
        {
            print($"Processing {i}...");
            Thread.Sleep(100);
        }
        print("Done.");
    }

    private string HeavyTask_ReturnString()
    {
        print("Start");
        for (int i = 0; i <5; i++)
        {
            print($"Processing {i}...");
            Thread.Sleep(100);
        }
        print("Done.");
        return "A message to be returned.";
    }

    /// <summary>
    /// 原版调用无返回
    /// </summary>
    public void task_no_async_no_return()
    {
        print("Start");
        Task.Run(HeavyTask_NoReturn);
        Thread.Sleep(250);
        print("Done.");
    }

    /// <summary>
    /// 原版调用有返回
    /// </summary>
    public void task_no_async_with_return()
    {
        print("Start");
        Task<string> task = Task.Run(HeavyTask_ReturnString);
        task.Wait();
        print("Done. Result: " + task.Result);
    }

    /// <summary>
    /// await 调用
    /// </summary>
    public async void task_basic_03()
    {
        print("Start");
        int v = 0;

        Task<string> task = Task.Run(() =>
        {
            print("Start v="+v);
            for (int i = 0; i <5; i++)
            {
                print($"Processing {i}...");
                Thread.Sleep(100);
            }
            print("Done.");
            return "A returned message.";
        });

        await Task.WhenAll(task);
        // task.Wait();
        print("Done. Result: " + task.Result);
    }


    #region async_typical_call_wait_returned_value
    /// <summary>
    /// 有参应用方式：异步调用后返回执行结果，并将结果在调用结束后在界面或控制台显示，返回结果再打印。
    /// </summary>
    void async_typical_call_wait_returned_value()
    {
        print("Start Calling AsyncMethodTypicalUse");
        var ResultTask = AsyncMethodTypicalUse();
        print($"Result={ResultTask.Result}");
    }


    private async Task<string> AsyncMethodTypicalUse()
    {
        string result = await Task.Run(() =>
        {
            print("SubTask: Start");
            for (int i = 0; i < 3; i++)
            {
                print($"SubTask: Processing {i} ...");
                Thread.Sleep(100);
            }
            print("SubTask: Done.");
            return "A returned message.";
        });
        print(result); // 控制台输出，也可以是界面元素如 txtResult.Text = result;
        return result;
    }
    #endregion

    #region async_typical_call_no_returned
    /// <summary>
    /// 有参应用方式：异步调用后返回执行结果，并将结果在调用结束后在界面或控制台显示，返回结果再打印。
    /// </summary>
    private void async_typical_call_no_returned()
    {
        print("Calling AsyncMethodTypicalUse");
        AsyncMethodTypicalUse02();
        print("Return and done.");
    }


    private async void AsyncMethodTypicalUse02()
    {
        print("Start");
        string result = await Running_TimeConsuming_Task_no_return();
        print(result); // 控制台输出，也可以是界面元素如 txtResult.Text = result;
    }
    #endregion


    public async void async_callback_will_complete_remaining_task_demo11()
    {
        Console.WriteLine("无参应用方式：异步调用后返回执行结果，并将结果在调用结束后在界面或控制台显示");
        var res = await Task.Run(() =>
        {
            Thread.Sleep(2000);
            return "Hello I am TimeConsumingMethod";
        });
        print("Returend: " + res);
    }

    public void async_callback_will_complete_remaining_task_demo()
    {
        print($"Start");
        AsyncTimeConsumingTask();
        Thread.Sleep(100);
        print($"Done");
    }

    public async void AsyncTimeConsumingTask()
    {
        print($"Start taskA = TimeConsumingMethod()");
        Task<int> taskA = Running_TimeConsuming_Task_with_return();
        print($"Await taskA");
        int a = await taskA;// 调用程序会在这里返回，等taskA完成后，通过SetResult返回，执行完成后再返回taskA，并完成剩下的任务。

        // 以下代码证明await会回调
        print("Completion Work: finish the following work after async call");
        for (int i = 0; i < 3; i++)
        {
            print($"Task {i} processing...");
            Thread.Sleep(100);
        }
        print("Completion Work: Done.");
        print($"Await Done, and Result a={a}");
    }

    void async_multiple_tasks_callback()
    {
        var tcs1 = new TaskCompletionSource<int>();
        var tcs2 = new TaskCompletionSource<int>();
        print("Start");
        AsyncTest2(tcs1, tcs2);

        Thread.Sleep(1000);
        tcs1.SetResult(100);

        Thread.Sleep(5000);
        tcs2.SetResult(100);
        print("Done");
    }

    async void AsyncTest2(TaskCompletionSource<int> tcs1, TaskCompletionSource<int> tcs2)
    {
        print("AsyncTest2 Start");

        int a = await Running_TimeConsuming_Task_with_return();
        print($"AsyncTest2 a={a}");

        //await后的线程没有变化，是因为SetResult比较早，这里可以直接同步执行（我认为的）
        int b = await tcs1.Task;
        print($"AsyncTest2 b={b}");

        //await后线程出现变化，异步执行（线程id不一样）
        int c = await tcs2.Task;
        print("AsyncTest2 c={c}, Done.");
    }


    private string task_await_check_isCompleted()
    {
        var task = Task.Run(() =>
       {
           print("SubTask: Start");
           for (int i = 0; i < 3; i++)
           {
               print($"SubTask: Processing {i} ...");
               Thread.Sleep(1000);
           }
           print("SubTask: Done.");
           return "A returned message.";
       });

        task.Wait(1000);
        if (task.IsCompleted)
        {
            print("task.IsCompleted.");
            return task.Result;
        }
        else
        {
            //task.Wait();
            print("Still running...");
            return "";
        }
    }

    /// <summary>
    /// 运行耗时的操作，返回 Task<string> 对象
    /// </summary>
    /// <returns></returns>
    public Task<string> Running_TimeConsuming_Task_no_return()
    {
        return Task.Run(() =>
        {
            print("SubTask: Start");
            for (int i = 1; i < 5; i++)
            {
                print($"SubTask: Processing {i} ...");
                Thread.Sleep(100*i);
            }
            print("SubTask: Done.");
            return "A returned message.";
        });
    }

    public Task<int> Running_TimeConsuming_Task_with_return()
    {
        // 定义任务完成的结果存储单元
        var tcs = new TaskCompletionSource<int>();
        new Thread(() =>
        {
            print($"SubThread: Start Processing");
            for (int i = 0; i < 3; i++)
            {
                print($"SubThread: Wait {i} s");
                Thread.Sleep(100);
            }
            print($"SubThread: Callback Start");
            tcs.SetResult(99); // 这里会回到 AsyncTest，执行剩下的任务，完成后才会返回并继续执行
            print($"SubThread: Callback Done");
        }).Start();

        return tcs.Task;
    }


    public async void async_crawl_web_with_cancellation_and_try_catch(int time_delay = 1200)
    {
        // ref: https://www.cnblogs.com/fanfan-90/p/12660996.html
        print($"Time Delay: {time_delay} ms.");
        CancellationTokenSource cts = new CancellationTokenSource();
        cts.CancelAfter(time_delay);
        HttpClient client = new HttpClient();
        print("await client.GetAsync");

        try
        {
            var res = await client.GetAsync("https://www.baidu.com", cts.Token);
            var result = await res.Content.ReadAsStringAsync();
            print("Done, Result: " + result?.Substring(0, 10) + "...");
        }
        catch (Exception ex)
        {
            print("Error: " + ex.Message);
        }
    }

    public async static Task Test()
    {
        // ref: https://www.cnblogs.com/fanfan-90/p/12660996.html
        CancellationTokenSource cts1 = new CancellationTokenSource();
        CancellationTokenSource cts2 = new CancellationTokenSource();
        CancellationTokenSource cts3 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);

        cts1.Token.Register(() => Console.WriteLine("cts1 Canceling"));
        cts2.Token.Register(() => Console.WriteLine("cts2 Canceling"));
        cts3.Token.Register(() => Console.WriteLine("cts3 Canceling"));

        cts2.CancelAfter(10);

        var res = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts1.Token);
        var result1 = await res.Content.ReadAsStringAsync();
        Console.WriteLine("cts1:{0}...", result1?.Substring(0, 10));

        var res2 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts2.Token);
        var result2 = await res2.Content.ReadAsStringAsync();
        Console.WriteLine("cts2:{0}...", result2?.Substring(0, 10));

        var res3 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts3.Token);
        var result3 = await res2.Content.ReadAsStringAsync();
        Console.WriteLine("cts3:{0}...", result3?.Substring(0, 10));
    }

    public void Is_Cancellation_Requested_check()
    {
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        CancellationToken cancellationToken = tokenSource.Token;

        // 取消时输出
        cancellationToken.Register(() => print("Cancelled."));

        //模拟传递的场景
        print("Start");
        Task.Run(
            async () =>
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    print("Running...");
                    await Task.Delay(400);
                }
            });

        //5s之后取消
        tokenSource.CancelAfter(3000);
    }

    /// <summary>
    /// 可以在某时刻，通过 CancellationTokenSource 的 Cancel 方法终止线程。
    /// 参考： https://www.cnblogs.com/yaopengfei/p/8229175.html
    /// </summary>
    public void cancellation_token_resource_direct_cancel()
    {
        CancellationTokenSource source = new CancellationTokenSource();
        //注册一个线程取消后执行的逻辑
        source.Token.Register(() =>
        {
            //这里执行线程被取消后的业务逻辑.
            print("-------------我是线程被取消后的业务逻辑---------------------");
            print($"output exit time: {DateTime.Now:yyyy/MM/dd HH:mm:ss}");
        });

        Task.Run(() =>
        {
            while (!source.IsCancellationRequested)
            {
                Thread.Sleep(100);
                print("Main task running...");
            }
        }, source.Token);

        Thread.Sleep(2000);
        source.Cancel();
    }

    public void cancellation_token_resource_delayed_cancel()
    {
        var source = new CancellationTokenSource();
        //也可以直接指定时间 var source = new CancellationTokenSource(4000);
        //注册一个线程取消后执行的逻辑
        source.Token.Register(() =>
        {
            //这里执行线程被取消后的业务逻辑.
            print("Cancelled.");
        });

        Task.Run(() =>
        {
            while (!source.IsCancellationRequested)
            {
                Thread.Sleep(100);
                print("working");
            }
        }, source.Token);

        //2s后自动取消， 实际上取消是有几十毫秒的延时
        print("Cancel after 2 seconds.");
        source.CancelAfter(TimeSpan.FromSeconds(2));
    }

    public void cancellation_token_resource_linked_cancel()
    {
        CancellationTokenSource source1 = new CancellationTokenSource();
        CancellationTokenSource source2 = new CancellationTokenSource();
        source2.Cancel();
        var combineSource = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
        Console.WriteLine($"source1.IsCancellationRequested       = {source1.IsCancellationRequested}");
        Console.WriteLine($"source2.IsCancellationRequested       = {source2.IsCancellationRequested}");
        Console.WriteLine($"combineSource.IsCancellationRequested = {combineSource.IsCancellationRequested}");

 
        //以下两种表达等价
        
        if (combineSource.IsCancellationRequested)
            Console.WriteLine("if:  报错了");

        try
        {
            combineSource.Token.ThrowIfCancellationRequested();
        }
        catch (Exception)
        {
            Console.WriteLine("try: 报错了");
        } 
 
    }


    /// <summary>
    /// 没搞明白。。。
    /// </summary>
    /// <param name="path"></param>
    public async void cancellation_token_source_useage(string path)
    {
        CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
        // cts.Cancel();
        print($"Downloading to {path}...");
        try
        {
            var s = await Task.Run(() =>
            {
                print("cts.Cancel()");
                cts.Cancel();
                print("Start");
                for (int i = 0; i < 10; i++)
                {
                    print($"processing {i}...");
                    Thread.Sleep(100);
                }
                print("Done.");
                return "";
            }, cts.Token);
            print(s);
        }
        catch (OperationCanceledException)
        {
            print($"Download canceled. Deleting {path}...");
        }

        print("Sleep");
        Thread.Sleep(350);
    }

}

